/**
 * @description 滚动列表循环复用item，且可无限循环组件
 * @date 2024.9.10
 * @author wz
 * @midify 2024.9.10
 * @editor wz
 */
import { _decorator, Component, Node, UITransform, instantiate, EventHandler, Enum, Tween, v3, EventTouch, tween, Vec3, math, EPSILON, CCInteger, CCFloat, CCBoolean } from 'cc';
import { M_Direction, V_Direction, H_Direction, V_Corner, H_Corner } from '../common/Enum';
const { ccclass, property } = _decorator;
/**
 * @deprecated 思路可以参考，建议使用CVirtualList
 */
@ccclass('CRoundScroll')
export class CRoundScroll extends Component {
    @property({ type: Node, tooltip: '滑动节点', group: 'CRoundScroll' })
    scroll: Node;
    @property({ type: Node, tooltip: 'mask节点', group: 'CRoundScroll' })
    mask: Node;
    @property({ type: Node, tooltip: 'item节点anchor(0.5,0.5)', group: 'CRoundScroll' })
    item: Node;

    @property({ type: CCBoolean, displayName: '滚动循环', tooltip: '滚动循环', group: 'CRoundScroll' })
    isRound: boolean = false;

    @property({ type: CCBoolean, displayName: '滚动惯性', tooltip: '滚动惯性', group: 'CRoundScroll' })
    inertia: boolean = false;

    @property({ type: Enum(M_Direction), displayName: '主方向', tooltip: '竖屏 ,横屏', group: 'CRoundScroll' })
    m_direction: M_Direction = M_Direction.VERTICAL;
    @property({ type: Enum(V_Direction), displayName: '垂直方向', tooltip: '从上到下或从下到上', visible() { return this.m_direction == M_Direction.VERTICAL }, group: 'CRoundScroll' })
    v_direction: V_Direction = V_Direction.TOP_TO_BOTTOM;
    @property({ type: Enum(H_Direction), displayName: '水平方向', tooltip: '从左到右或从右到左', visible() { return this.m_direction == M_Direction.HORIZONTAL }, group: 'CRoundScroll' })
    h_direction: H_Direction = H_Direction.LEFT_TO_RIGHT;
    @property({ type: Enum(V_Corner), displayName: '垂直方向StartCorner', tooltip: '从左往右或从右往左', visible() { return this.m_direction == M_Direction.VERTICAL }, group: 'CRoundScroll' })
    v_corner: V_Corner = V_Corner.START_LEFT_RIGHT;
    @property({ type: Enum(H_Corner), displayName: '水平方向StartCorner', tooltip: '从左往右或从右往左', visible() { return this.m_direction == M_Direction.HORIZONTAL }, group: 'CRoundScroll' })
    h_corner: H_Corner = H_Corner.START_TOP_BOTTOM;

    @property({ type: CCInteger, displayName: '纵向布局时的列数', tooltip: '纵向布局时的列数', visible() { return this.m_direction == M_Direction.VERTICAL }, group: 'CRoundScroll' })
    colNum: number = 1;
    @property({ type: CCInteger, displayName: '横向布局时的行数', tooltip: '横向布局时的行数', visible() { return this.m_direction == M_Direction.HORIZONTAL }, group: 'CRoundScroll' })
    rowNum: number = 1;

    @property({ type: CCFloat, displayName: 'paddingLeft', tooltip: '左边界和节点内边距', group: 'CRoundScroll' })
    paddingLeft: number = 0;
    @property({ type: CCFloat, displayName: 'paddingRight', tooltip: '右边界和节点内边距', group: 'CRoundScroll' })
    paddingRight: number = 0;
    @property({ type: CCFloat, displayName: 'paddingTop', tooltip: '上边界和节点内边距', group: 'CRoundScroll' })
    paddingTop: number = 0;
    @property({ type: CCFloat, displayName: 'paddingBottom', tooltip: '下边界和节点内边距', group: 'CRoundScroll' })
    paddingBottom: number = 0;
    @property({ type: CCFloat, displayName: 'spacingY', tooltip: '纵向间隔', group: 'CRoundScroll' })
    spacingY: number = 0;
    @property({ type: CCFloat, displayName: 'spacingX', tooltip: '横向间隔', group: 'CRoundScroll' })
    spacingX: number = 0
    @property({ type: [EventHandler], tooltip: '刷新响应事件', group: 'CRoundScroll' })
    loopEvents: EventHandler[] = [];


    get itemWidth(): number {
        return this.item.getComponent(UITransform).width;
    };
    get itemHeight(): number {
        return this.item.getComponent(UITransform).height;
    };

    private _initialled: boolean = false;
    private _initScrollEvents: boolean = false;



    private _itemCount: number = 0;
    private _itemList: { originIndex: number, index: number, node: Node, isEvent: boolean }[] = [];
    private _storageList: { originIndex: number, index: number, node: Node, isEvent: boolean }[] = [];
    protected _dataList: any[] = [];
    private _touching: boolean = false;
    protected _scrollOffset: number = 0;
    private _scrollSpeed: number = 0;
    private _timestamp: number = 0;
    private _autoScroll: boolean = false;
    private _autoScrollTime: number = 0;
    private _maxAutoScollTIme: number = 2;
    private _updateCallback: (item: Node, index: number, data: any) => void = null;
    //注册事件
    private initScrollEvents() {
        if (this._initScrollEvents) return;
        this._initScrollEvents = true;
        this.scroll.on(Node.EventType.TOUCH_START, this.onTouchStart.bind(this));
        this.scroll.on(Node.EventType.TOUCH_MOVE, this.onTouchMove.bind(this));
        this.scroll.on(Node.EventType.TOUCH_END, this.onTouchEnd.bind(this));
        this.scroll.on(Node.EventType.TOUCH_CANCEL, this.onTouchEnd.bind(this));
    }

    /**
     * 初始化设置
     */
    private init() {
        if (this._initialled) return;
        this._initialled = true;
        this.item.active = false
        let scrollTrans = this.scroll.getComponent(UITransform);
        if (this.m_direction == M_Direction.VERTICAL) {
            let contentWith = this.paddingLeft + this.itemWidth * this.colNum + this.spacingX * (this.colNum - 1) + this.paddingRight;
            this.mask.getComponent(UITransform).width = contentWith;
            let count = Math.ceil(scrollTrans.height / (this.itemHeight + this.spacingY)) + 2;
            this._itemCount = count * this.colNum;

            if (this.v_direction == V_Direction.TOP_TO_BOTTOM) {
                this.mask.getComponent(UITransform).setAnchorPoint(0.5, 1);
                this.mask.setPosition(v3(0, scrollTrans.height / 2))
            }
            else if (this.v_direction == V_Direction.BOTTOM_TO_TOP) {
                this.mask.getComponent(UITransform).setAnchorPoint(0.5, 0);
                this.mask.setPosition(v3(0, -scrollTrans.height / 2))
            }
        }
        else if (this.m_direction == M_Direction.HORIZONTAL) {
            let contentHeight = this.paddingTop + this.itemHeight * this.rowNum + this.spacingY * (this.rowNum - 1) + this.paddingBottom;
            this.mask.getComponent(UITransform).height = contentHeight
            let count = Math.ceil(scrollTrans.width / (this.itemWidth + this.spacingX)) + 2;
            this._itemCount = count * this.rowNum;

            if (this.h_direction == H_Direction.LEFT_TO_RIGHT) {
                this.mask.getComponent(UITransform).setAnchorPoint(0, 0.5);
                this.mask.setPosition(v3(-scrollTrans.width / 2, 0))
            }
            else if (this.h_direction == H_Direction.RIGHT_TO_LEFT) {
                this.mask.getComponent(UITransform).setAnchorPoint(1, 0.5);
                this.mask.setPosition(v3(scrollTrans.width / 2, 0))
            }
        }
    }

    /**
   * 计算并生成item
   */
    private generateItem() {
        const needCount = this._itemCount - this._itemList.length;
        if (needCount > 0) {
            for (let i = 0; i < needCount; i++) {
                if (this._storageList.length > 0) {
                    this._itemList.push(this._storageList[0]);
                    this._storageList.splice(0, 1);
                }
                else {
                    const tempItem: Node = instantiate(this.item);
                    tempItem.parent = this.item.parent;
                    tempItem.active = true;
                    this._itemList.push({ originIndex: 0, index: 0, node: tempItem, isEvent: true });
                }
            }
        }
        else if (needCount < 0) {
            while (this._itemList.length > -needCount) {
                let item = this._itemList.shift()
                item.node.active = false;
                this._storageList.push(item);
            }
        }
        for (let i = 0; i < this._itemList.length; i++) {
            this._itemList[i].node.active = true;
            this._itemList[i].originIndex = i;
            this._itemList[i].isEvent = true;
        }
    }

    private getMaxScroll() {
        let maxScroll = 0, length = 0;
        if (this.m_direction == M_Direction.VERTICAL) {
            length = Math.ceil(this._dataList.length / this.colNum)
            maxScroll = this.paddingTop + this.itemHeight * length + this.spacingY * (length - 1) + this.paddingBottom - this.mask.getComponent(UITransform).height
        }
        else if (this.m_direction == M_Direction.HORIZONTAL) {
            length = Math.ceil(this._dataList.length / this.rowNum)
            maxScroll = this.paddingLeft + this.itemWidth * length + this.spacingX * (length - 1) + this.paddingRight - this.mask.getComponent(UITransform).width
        }
        return maxScroll
    }

    private getOutOfDis() {
        let dis = 0, offset = this._scrollOffset, maxScroll = this.getMaxScroll();

        if ((this.m_direction == M_Direction.VERTICAL && this.v_direction == V_Direction.BOTTOM_TO_TOP) ||
            (this.m_direction == M_Direction.HORIZONTAL && this.h_direction == H_Direction.LEFT_TO_RIGHT))
            offset = -offset

        if (offset < 0)
            dis = offset;
        else if (offset > maxScroll)
            dis = offset - maxScroll;

        return dis;
    }

    private getOutToBorderOffset() {
        let offset = 0, maxScroll = this.getMaxScroll();
        if (maxScroll < 0) return offset;
        if (this.m_direction == M_Direction.VERTICAL) {
            if (this.v_direction == V_Direction.TOP_TO_BOTTOM) {
                if (this._scrollOffset < 0)
                    offset = 0
                else if (this._scrollOffset > maxScroll)
                    offset = maxScroll
            }
            else if (this.v_direction == V_Direction.BOTTOM_TO_TOP) {
                if (this._scrollOffset > 0)
                    offset = 0
                else if (-this._scrollOffset > maxScroll)
                    offset = -maxScroll
            }
        }
        else if (this.m_direction == M_Direction.HORIZONTAL) {
            if (this.h_direction == H_Direction.LEFT_TO_RIGHT) {
                if (this._scrollOffset > 0)
                    offset = 0
                else if (-this._scrollOffset > maxScroll)
                    offset = -maxScroll
            }
            else if (this.h_direction == H_Direction.RIGHT_TO_LEFT) {
                if (this._scrollOffset < 0)
                    offset = 0
                else if (this._scrollOffset > maxScroll)
                    offset = maxScroll
            }
        }
        return offset;
    }

    private elastic(ratio: number) {
        return ratio * ratio;
    }

    private deltaElastic(delta: number) {
        if (this.isRound)
            return delta;

        let dis = this.getOutOfDis();
        let temp = delta;
        if ((this.m_direction == M_Direction.VERTICAL && this.v_direction == V_Direction.BOTTOM_TO_TOP) ||
            (this.m_direction == M_Direction.HORIZONTAL && this.h_direction == H_Direction.LEFT_TO_RIGHT))
            temp = -delta;

        if (dis * temp > 0)
            delta *= 1 - this.elastic(math.clamp01(Math.abs(dis) / 100.0));
        return delta;
    }

    private moveToBorder() {
        if (this._scrollOffset != 0 && !this.isRound) {
            let scrollOffset = this._scrollOffset;
            let targetOffset = this.getOutToBorderOffset();
            Tween.stopAllByTarget(this.node);
            tween(this.node)
                .to(0.2, {}, {
                    onUpdate: (target, ratio: number) => {
                        this._scrollOffset = math.lerp(scrollOffset, targetOffset, ratio)
                        this.updateItem();
                    }
                })
                .start()
        }
    }


    protected onTouchStart(event: EventTouch) {
        Tween.stopAllByTarget(this.node);
        this._touching = true;
        this._autoScroll = false;
        this._timestamp = new Date().getTime() / 1000;
    }

    protected onTouchMove(event: EventTouch) {
        if (this._touching) {
            let delta = this.m_direction == M_Direction.HORIZONTAL ? event.getDelta().x : event.getDelta().y
            delta = this.deltaElastic(delta);
            this._scrollOffset += delta;

            let timestamp = new Date().getTime() / 1000;
            this._scrollSpeed = delta / Math.max(0.001, timestamp - this._timestamp);
            this._timestamp = timestamp;

            console.log('0------_scrollOffset:' + this._scrollOffset + '-------delta:' + delta);
            this.updateItem();
        }
    }

    protected onTouchEnd(event: EventTouch) {
        this._touching = false;

        //列表状态滑动出界回复运动
        if (!this.isRound && this.getOutOfDis() != 0) {
            this.moveToBorder();
            return;
        }

        this._autoScroll = true;
        this._autoScrollTime = this._maxAutoScollTIme;
    }



    protected update(deltaTime: number) {
        if (this._autoScroll && this.inertia) {
            this._autoScrollTime -= deltaTime
            if (this._autoScrollTime < 0) {
                this._autoScroll = false;
                this._autoScrollTime = 0;
            }

            let factor = this._autoScrollTime / this._maxAutoScollTIme
            this._scrollSpeed *= this.elastic(factor);
            let delta = this._scrollSpeed * deltaTime;
            delta = this.deltaElastic(delta)

            this._scrollOffset += delta
            console.log('factor:' + factor + '------_scrollSpeed:' + this._scrollSpeed)
            console.log('1------_scrollOffset:' + this._scrollOffset + '-------delta:' + delta);
            this.updateItem();

            //列表状态,惯性滑动出界且慢慢停止时回复到边界运动
            if (!this.isRound && Math.abs(delta) < EPSILON && this.getOutOfDis() != 0) {
                this._autoScroll = false;
                this._autoScrollTime = 0;
                this.moveToBorder();
            }
        }
    }

    /**
     * 更新item
     */
    protected updateItem() {
        if (this.m_direction == M_Direction.VERTICAL) {
            if (this.v_direction == V_Direction.TOP_TO_BOTTOM)
                this.update_top_to_bottom();
            else if (this.v_direction == V_Direction.BOTTOM_TO_TOP)
                this.update_bottom_to_top()
        }
        else if (this.m_direction == M_Direction.HORIZONTAL) {
            if (this.h_direction == H_Direction.LEFT_TO_RIGHT)
                this.update_left_to_right();
            else if (this.h_direction == H_Direction.RIGHT_TO_LEFT)
                this.update_right_to_left()
        }
    }

    /**
       * 发射事件
       */
    private emitItem(item: { index: number, node: Node, isEvent: boolean }) {
        if (item.node.active && item.isEvent) {
            this.loopEvents.forEach((a) => {
                if (a.component != '' || a._componentName) {
                    let script: any = item.node.getComponent(a._componentName);
                    //绑在item上的事件替换下触发节点
                    if (script)
                        a.target = item.node
                    a.emit([item.node, item.index, this._dataList[item.index]])
                }
            })

            if (this._updateCallback)
                this._updateCallback(item.node, item.index, this._dataList[item.index]);
        }
        item.isEvent = false;
    }

    private update_top_to_bottom() {
        if (this.m_direction == M_Direction.VERTICAL && this.v_direction == V_Direction.TOP_TO_BOTTOM) {
            const contenTrans = this.mask.getComponent(UITransform);
            let length = this._dataList.length;
            let unit = this.itemHeight + this.spacingY;

            let offsetIndex = 0, offsetPos = 0;
            if (this.isRound) {
                offsetIndex = Math.floor(this._scrollOffset / unit);
                offsetPos = (this._scrollOffset % unit + unit) % unit;
            }
            else {
                offsetIndex = this._scrollOffset < 0 ? 0 : Math.floor(Math.abs(this._scrollOffset) / unit);
                offsetPos = this._scrollOffset < 0 ? this._scrollOffset : this._scrollOffset % unit;
            }

            //初始偏移
            let paddingIndex = Math.floor(-this.paddingTop / unit);
            offsetIndex += paddingIndex;
            offsetPos -= paddingIndex * unit;

            for (let i = 0; i < this._itemList.length; i++) {
                let item = this._itemList[i];
                let originIndex = item.originIndex;
                let node = this._itemList[i].node;
                let rowIndex = Math.floor(originIndex / this.colNum);
                let colIndex = originIndex % this.colNum;
                let posX, posY;
                if (this.v_corner == V_Corner.START_LEFT_RIGHT)
                    posX = -contenTrans.width / 2 + this.paddingLeft + (colIndex + 0.5) * this.itemWidth + colIndex * this.spacingX;
                else
                    posX = contenTrans.width / 2 - this.paddingRight - (colIndex + 0.5) * this.itemWidth - colIndex * this.spacingX;
                posY = -(this.paddingTop + (rowIndex + 0.5) * this.itemHeight + rowIndex * this.spacingY);
                node.setPosition(posX, posY + offsetPos);

                let index = originIndex + offsetIndex * this.colNum;
                if (this.isRound) index = (index % length + length) % length;

                item.isEvent ||= item.index != index;
                item.index = index;

                //超界隐藏
                node.active = index >= 0 && index < length;
                this.emitItem(this._itemList[i])
            }
        }
    }

    private update_bottom_to_top() {
        if (this.m_direction == M_Direction.VERTICAL && this.v_direction == V_Direction.BOTTOM_TO_TOP) {
            const contenTrans = this.mask.getComponent(UITransform);
            let length = this._dataList.length;
            let unit = this.itemHeight + this.spacingY;

            let offsetIndex = 0, offsetPos = 0;
            if (this.isRound) {
                offsetIndex = Math.floor(-this._scrollOffset / unit);
                offsetPos = (this._scrollOffset % unit - unit) % unit;
            }
            else {
                offsetIndex = this._scrollOffset > 0 ? 0 : Math.floor(Math.abs(this._scrollOffset) / unit);
                offsetPos = this._scrollOffset > 0 ? this._scrollOffset : this._scrollOffset % unit
            }

            //初始偏移
            let paddingIndex = Math.floor(-this.paddingBottom / unit);
            offsetIndex += paddingIndex;
            offsetPos += paddingIndex * unit;

            for (let i = 0; i < this._itemList.length; i++) {
                let item = this._itemList[i];
                let originIndex = item.originIndex;
                let node = this._itemList[i].node;
                let rowIndex = Math.floor(originIndex / this.colNum);
                let colIndex = originIndex % this.colNum;
                let posX, posY;
                if (this.v_corner == V_Corner.START_LEFT_RIGHT)
                    posX = -contenTrans.width / 2 + this.paddingLeft + (colIndex + 0.5) * this.itemWidth + colIndex * this.spacingX;
                else
                    posX = contenTrans.width / 2 - this.paddingRight - (colIndex + 0.5) * this.itemWidth - colIndex * this.spacingX;
                posY = this.paddingBottom + (rowIndex + 0.5) * this.itemHeight + rowIndex * this.spacingY;
                node.setPosition(posX, posY + offsetPos);

                let index = originIndex + offsetIndex * this.colNum;
                if (this.isRound) index = (index % length + length) % length;

                item.isEvent ||= item.index != index;
                item.index = index;

                //超界隐藏
                node.active = index >= 0 && index < length;
                this.emitItem(this._itemList[i])
            }
        }
    }

    private update_left_to_right() {
        if (this.m_direction == M_Direction.HORIZONTAL && this.h_direction == H_Direction.LEFT_TO_RIGHT) {
            const contenTrans = this.mask.getComponent(UITransform);
            let length = this._dataList.length;
            let unit = this.itemWidth + this.spacingX;

            let offsetIndex = 0, offsetPos = 0;
            if (this.isRound) {
                offsetIndex = Math.floor(-this._scrollOffset / unit);
                offsetPos = (this._scrollOffset % unit - unit) % unit;
            }
            else {
                offsetIndex = this._scrollOffset > 0 ? 0 : Math.floor(Math.abs(this._scrollOffset) / unit);
                offsetPos = this._scrollOffset > 0 ? this._scrollOffset : this._scrollOffset % unit
            }

            //初始偏移
            let paddingIndex = Math.floor(-this.paddingLeft / unit);
            offsetIndex += paddingIndex;
            offsetPos += paddingIndex * unit;

            for (let i = 0; i < this._itemList.length; i++) {
                let item = this._itemList[i];
                let originIndex = item.originIndex;
                let node = this._itemList[i].node;
                let rowIndex = originIndex % this.rowNum;
                let colIndex = Math.floor(originIndex / this.rowNum);
                let posX, posY;
                posX = this.paddingLeft + (colIndex + 0.5) * this.itemWidth + colIndex * this.spacingX;
                if (this.h_corner == H_Corner.START_TOP_BOTTOM)
                    posY = contenTrans.height / 2 - this.paddingTop - (rowIndex + 0.5) * this.itemHeight - rowIndex * this.spacingY;
                else
                    posY = -contenTrans.height / 2 + this.paddingBottom + (rowIndex + 0.5) * this.itemHeight + rowIndex * this.spacingY;
                node.setPosition(posX + offsetPos, posY);

                let index = originIndex + offsetIndex * this.rowNum;
                if (this.isRound) index = (index % length + length) % length;

                item.isEvent ||= item.index != index;
                item.index = index;

                //超界隐藏
                node.active = index >= 0 && index < length;
                this.emitItem(this._itemList[i])
            }
        }
    }

    private update_right_to_left() {
        if (this.m_direction == M_Direction.HORIZONTAL && this.h_direction == H_Direction.RIGHT_TO_LEFT) {
            const contenTrans = this.mask.getComponent(UITransform);
            let length = this._dataList.length;
            let unit = this.itemWidth + this.spacingX;

            let offsetIndex = 0, offsetPos = 0;
            if (this.isRound) {
                offsetIndex = Math.floor(this._scrollOffset / unit);
                offsetPos = (this._scrollOffset % unit + unit) % unit;
            }
            else {
                offsetIndex = this._scrollOffset < 0 ? 0 : Math.floor(Math.abs(this._scrollOffset) / unit);
                offsetPos = this._scrollOffset < 0 ? this._scrollOffset : this._scrollOffset % unit
            }

            //初始偏移
            let paddingIndex = Math.floor(-this.paddingRight / unit);
            offsetIndex += paddingIndex;
            offsetPos -= paddingIndex * unit;

            for (let i = 0; i < this._itemList.length; i++) {
                let item = this._itemList[i];
                let originIndex = item.originIndex;
                let node = this._itemList[i].node;
                let rowIndex = originIndex % this.rowNum;
                let colIndex = Math.floor(originIndex / this.rowNum);
                let posX, posY;
                posX = -(this.paddingRight + (colIndex + 0.5) * this.itemWidth + colIndex * this.spacingX);
                if (this.h_corner == H_Corner.START_TOP_BOTTOM)
                    posY = contenTrans.height / 2 - this.paddingTop - (rowIndex + 0.5) * this.itemHeight - rowIndex * this.spacingY;
                else
                    posY = -contenTrans.height / 2 + this.paddingBottom + (rowIndex + 0.5) * this.itemHeight + rowIndex * this.spacingY;
                node.setPosition(posX + offsetPos, posY);

                let index = originIndex + offsetIndex * this.rowNum;
                if (this.isRound) index = (index % length + length) % length;

                item.isEvent ||= item.index != index;
                item.index = index;

                //超界隐藏
                node.active = index >= 0 && index < length;
                this.emitItem(this._itemList[i])
            }
        }
    }

    /**
      * 获取节点
      */
    getValidItemInfo() {
        return this._itemList.filter(a => a.node.active)
    }

    /**
     * 设置更新item的回调
     * @param callback 
     */
    setUpdateItemCallback(callback: (item: Node, index: number, data: any) => void) {
        this._updateCallback = callback
    }

    /**
     * 刷新数据显示
     * @param dataList 显示的数据
     * @param delayTime 延迟显示item时间 
     */
    updateData(dataList: any[]) {
        this.initScrollEvents();
        this.init();

        this._dataList = dataList;

        this.generateItem();
        this.updateItem();

        //超界或者在内部移动到边界
        if (this.getMaxScroll() < 0 || this.getOutOfDis() != 0) {
            this._touching = false;
            this._autoScroll = false;
            this._autoScrollTime = 0;
            this.moveToBorder();
        }
    }
}


